package hydrap2p.library;

import hydrap2p.helper.RangeUtils;
import hydrap2p.library.listening.ListenerManager;
import hydrap2p.logging.Level;

import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class DownloadHashChecker implements Runnable, NewHashListener{

	private List<Long> scanqueue;
	private Map<String, Thread> newhashthreads; 

	private MyBoolean running;
	private MyBoolean scanning;
	
	public DownloadHashChecker()
	{
		scanqueue=new LinkedList<Long>();
		newhashthreads=new TreeMap<String, Thread>();
		
		running=new MyBoolean(false);
		scanning=new MyBoolean(false);
		
		Hashers.getInstance().registerListener(this);
	}

	private void setScanning(boolean scanning)
	{
		synchronized(this.scanning)
		{
			this.scanning.setState(scanning);
		}
	}
	public boolean isScanning()
	{
		boolean ret=false;
		synchronized(this.scanning)
		{
			ret=this.scanning.getState();
		}
		return ret;
	}
	public boolean isRunning()
	{
		boolean ret;
		synchronized(running)
		{
			ret=running.getState();
		}
		return ret;
	}
	
	public void stopRunning()
	{
		synchronized(running)
		{
			running.setState(false);
		}
		synchronized(scanqueue)
		{
			scanqueue.notify();
		}
	}

	public void enqueue(long uid)
	{
		synchronized(scanqueue)
		{
			if (!scanqueue.contains(uid))
			{
				scanqueue.add(uid);
				scanqueue.notify();
			}
		}
	}
	
	@Override
	public void run() {
		long cur=0;
		running.setState(true);
		while (isRunning())
		{
			synchronized(scanqueue)
			{
				if (isRunning() && scanqueue.size()==0)
				{
					setScanning(false);
					try {
						scanqueue.wait();
					} catch (InterruptedException e) {}
				}
				if (isRunning() && scanqueue.size()>0)
				{
					setScanning(true);
					cur=scanqueue.remove(0);
				}
			}
			if (isScanning())
			{
				checkHashes(cur);
			}
		}
	}
	
	private void checkHashes(long cur)
	{
		SafeDatabase safedb;
		try {
			safedb = SafeDatabase.getInstance();
		} catch (Exception e) {
			LibraryLogging.log(Level.ERROR, "Library - Download Hash Checker", "Failed to load the database");
			return;
		}
		boolean changed=false;
		ItemInfo item=safedb.load(cur, true);
		if (item==null)
			return;
		List<Range> downloaded=safedb.getDownloadedRanges(item.uid);
		List<Range> verified=safedb.getVerifiedRanges(item.uid);
		List<Range> newverified=new LinkedList<Range>();
		List<Range> newfailed=new LinkedList<Range>();
		for (HashDataEx hash:item.targetHashesEx)
		{
			if (!hash.isVerified)
			{
				if (RangeUtils.contains(downloaded, hash.range))
				{
					hash.isDownloaded=true;
					changed=true;
					
					if (Hashers.getInstance().getHashers().contains(hash.algorithm))
					{
						boolean verifiedthis=Hashers.getInstance().verifyHash(item, hash);
						if (verifiedthis)
						{
							verified=RangeUtils.merge(verified, hash.range);
							hash.isVerified=true;
							newverified.add(hash.range);
						}
						else
						{
							downloaded=RangeUtils.remove(downloaded, hash.range);
							newfailed.add(hash.range);
						}
					}
				}
			}
		}
		if (newverified.size()>0)
			safedb.setVerifiedRanges(item.uid, verified);
		if (newfailed.size()>0)
			safedb.setDownloadedRanges(item.uid, downloaded);
		if (changed)
			safedb.save(item);
		safedb.release(item);
		
		ListenerManager listeners=ListenerManager.getInstance();
		for(Range newrange:newverified)
		{
			listeners.newVerifiedProgress(item, newrange);
		}
		for (Range newrange:newfailed)
		{
			listeners.deletePartialProgress(item, newrange);
		}
	}

	@Override
	public void newHash(String algorithm) {
		synchronized(newhashthreads)
		{
			if (!newhashthreads.containsKey(algorithm))
			{
				Thread temp=new NewHashScanner(this, algorithm);
				temp.start();
				newhashthreads.put(algorithm, temp);
			}
		}
	}
	
	private static class NewHashScanner extends Thread
	{
		private String algorithm;
		private DownloadHashChecker checker;
		public NewHashScanner(DownloadHashChecker checker, String algorithm)
		{
			super();
			this.algorithm=algorithm;
			this.checker=checker;
		}
		
		@Override
		public void run()
		{
			List<Long> list;
			try {
				list = Database.getInstance().searchDownloadedUnhashed(algorithm);
			} catch (SQLException e) {
				LibraryLogging.log(Level.ERROR, "Library - Download Hash Checker", "Failed to load the database");
				return;
			}
			if (list!=null)
			{
				for (long uid:list)
					checker.enqueue(uid);					
			}
				
				
		}
	}
}
